package com.ruowei.modules.sys.web;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruowei.common.error.exception.CommonException;
import com.ruowei.config.Constants;
import com.ruowei.modules.sys.domain.LoginRelatedLog;
import com.ruowei.modules.sys.domain.SysUserRole;
import com.ruowei.modules.sys.domain.enumeration.UserStatusType;
import com.ruowei.modules.sys.domain.enumeration.UserType;
import com.ruowei.modules.sys.domain.table.SysUserTable;
import com.ruowei.modules.sys.pojo.vm.LoginVM;
import com.ruowei.modules.sys.repository.LoginRelatedLogRepository;
import com.ruowei.modules.sys.repository.SysRoleRepository;
import com.ruowei.modules.sys.repository.SysUserRepository;
import com.ruowei.modules.sys.repository.SysUserRoleRepository;
import com.ruowei.modules.sys.repository.table.SysUserTableRepository;
import com.ruowei.modules.sys.service.UserJWTService;
import com.ruowei.security.UserModel;
import com.ruowei.security.jwt.JWTFilter;
import com.ruowei.security.jwt.TokenProvider;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import javax.validation.Valid;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;

/**
 * Controller to authenticate users.
 */
@RestController
@RequestMapping("/api")
@Api(tags = Constants.TAG_LOGIN)
@Transactional
public class UserJWTController {

    private final TokenProvider tokenProvider;
    private final SysUserTableRepository sysUserTableRepository;
    private final SysUserRepository sysUserRepository;
    private final LoginRelatedLogRepository loginRelatedLogRepository;
    private final SysRoleRepository sysRoleRepository;
    private final SysUserRoleRepository sysUserRoleRepository;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final UserJWTService userJWTService;

    public UserJWTController(TokenProvider tokenProvider, SysUserTableRepository sysUserTableRepository,
                             SysUserRepository sysUserRepository, LoginRelatedLogRepository loginRelatedLogRepository,
                             SysRoleRepository sysRoleRepository, SysUserRoleRepository sysUserRoleRepository,
                             AuthenticationManagerBuilder authenticationManagerBuilder,
                             UserJWTService userJWTService) {
        this.tokenProvider = tokenProvider;
        this.sysUserTableRepository = sysUserTableRepository;
        this.sysUserRepository = sysUserRepository;
        this.loginRelatedLogRepository = loginRelatedLogRepository;
        this.sysRoleRepository = sysRoleRepository;
        this.sysUserRoleRepository = sysUserRoleRepository;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
        this.userJWTService = userJWTService;
    }

    @PostMapping("/authenticate")
    @ApiOperation(value = "登录", notes = "作者：李春浩 ，修改：曹状状")
    public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {

        Optional<SysUserTable> sysUserTableOpt = sysUserTableRepository
            .findOneByLoginCodeAndStatus(loginVM.getUsername(), UserStatusType.NORMAL);
        // 登录错误5次，锁定30分钟
        if (sysUserTableOpt.isPresent()
            && sysUserTableOpt.get().getAttempt() != null && sysUserTableOpt.get().getAttempt() >= 5
            && sysUserTableOpt.get().getLastAttempt().plus(30, ChronoUnit.MINUTES).isAfter(Instant.now())) {
            throw new LockedException("您的账号已被锁定30分钟");
        }

        UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());

        Authentication authentication;
        try {
            authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        } catch (Exception e) {
            sysUserTableOpt.ifPresent(sysUserTable -> {
                if (sysUserTable.getAttempt() == null) {
                    sysUserTable.setAttempt(1);
                } else {
                    sysUserTable.setAttempt(sysUserTable.getAttempt() + 1);
                }
                sysUserTable.setLastAttempt(Instant.now());
                sysUserTableRepository.save(sysUserTable);
            });
            throw e;
        }
        SecurityContextHolder.getContext().setAuthentication(authentication);
        boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
        String jwt = tokenProvider.createToken(authentication, rememberMe);
        // 用户信息
        JWTToken jwtToken = new JWTToken();
        jwtToken.setIdToken(jwt);

        sysUserTableOpt.ifPresent(sysUser -> {
            sysUser.setAttempt(0);
            sysUserTableRepository.save(sysUser);

            if (loginVM.getUserTypes().isEmpty() || !loginVM.getUserTypes().contains(sysUser.getUserType())) {
                throw new CommonException("没有登录权限");
            }

            jwtToken.setUserName(sysUser.getUserName());
            jwtToken.setEmail(sysUser.getEmail());
            jwtToken.setMobile(sysUser.getMobile());
            jwtToken.setPhone(sysUser.getPhone());
            jwtToken.setUserType(sysUser.getUserType());
            jwtToken.setUserCode(sysUser.getUserCode());

            //生成登入日志
            LoginRelatedLog loginRelatedLog = new LoginRelatedLog();
            ZonedDateTime nowTime = ZonedDateTime.now(ZoneId.systemDefault());
            loginRelatedLog.setLoginCode(sysUser.getLoginCode());
            loginRelatedLog.setOperateTime(nowTime);
            loginRelatedLog.setOperateType("登入");
            loginRelatedLog.setUserName(sysUser.getUserName());
            // 获取userRole
            Long userId = sysUser.getId();
            List<SysUserRole> sysUserRoles = sysUserRoleRepository.findAllBySysUserId(userId);
            if (!sysUserRoles.isEmpty()) {
                Long roleId = sysUserRoles.get(0).getSysRoleId();
                String roleName = null;
                if (sysRoleRepository.findById(roleId).isPresent()) {
                    roleName = sysRoleRepository.findById(roleId).get().getRoleName();
                }
                loginRelatedLog.setUserRole(roleName);
                // 保存登入日志
                loginRelatedLogRepository.save(loginRelatedLog);
            } else {
                throw new LockedException("未授权用户无法登录系统");
            }
        });
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);


        return new ResponseEntity<>(jwtToken, httpHeaders, HttpStatus.OK);
    }

    @PostMapping("/logout")
    @ApiOperation(value = "登出", notes = "作者：曹状状")
    public ResponseEntity<Void> logOut(@ApiIgnore @AuthenticationPrincipal UserModel userModel) {
        sysUserRepository.findById(userModel.getId())
            .ifPresent(sysUser -> {
                userJWTService.logout(sysUser);
                userJWTService.offline(sysUser);
            });
        return ResponseEntity.ok().build();
    }


    /**
     * Object to return as body in JWT Authentication.
     */
    static class JWTToken {

        private String idToken;
        private String userName;
        private String email;
        private String mobile;
        private String phone;
        private UserType userType;
        private String userCode;

        @JsonProperty("id_token")
        String getIdToken() {
            return idToken;
        }

        public String getUserCode() {
            return userCode;
        }

        public void setUserCode(String userCode) {
            this.userCode = userCode;
        }

        void setIdToken(String idToken) {
            this.idToken = idToken;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getMobile() {
            return mobile;
        }

        public void setMobile(String mobile) {
            this.mobile = mobile;
        }

        public String getPhone() {
            return phone;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }

        public UserType getUserType() {
            return userType;
        }

        public void setUserType(UserType userType) {
            this.userType = userType;
        }
    }
}
